using System;
using System.Collections.Generic;
using System.Text;

namespace Multiplicity.Reflection {
    static class CodedToken {

        public static uint ReadToken(MetadataParser parser, CodedTokenFamily tokenFamily) {

            uint maxRowCount = MaxRowCount(parser, tokenFamily.Tables);
            uint codedToken = 0;

            if (maxRowCount <= (ushort.MaxValue & (~tokenFamily.TagMask))) {
                codedToken = parser.ReadUInt16();
            }
            else {
                codedToken = parser.ReadUInt32();
            }

            return DecodeToken(codedToken, tokenFamily);
        }

        public static uint DecodeToken(uint codedToken, CodedTokenFamily tokenFamily) {
            uint tableTag = codedToken & tokenFamily.TagMask;
            MetadataTableType table = tokenFamily.Tables[tableTag];

            return ((uint)(table) << 24) | (codedToken >> tokenFamily.TagSize);
        }

        static uint MaxRowCount(MetadataParser parser, MetadataTableType[] tables) {
            uint maxRowCount = 0;

            foreach (MetadataTableType t in tables) {
                if (parser.TableExists(t)) {
                    uint rowCount = parser.TableRowCount(t);

                    if (rowCount > maxRowCount)
                        maxRowCount = rowCount;
                }
            }

            return maxRowCount;
        }

    }

    enum CodedTokenFamilyType : byte {
        TypeDefOrRef = 64,
        ResolutionScope = 75,
    }

    struct CodedTokenFamily {
        
        public readonly MetadataTableType[] Tables;
        public readonly uint TagMask;
        public readonly byte TagSize;
        public readonly byte ID;

        CodedTokenFamily(CodedTokenFamilyType id, byte tagSize, uint tagMask, params MetadataTableType[] tables) {
            this.ID = (byte)(id);
            this.TagMask = tagMask;
            this.TagSize = tagSize;
            this.Tables = tables;
        }

        public static readonly CodedTokenFamily TypeDefOrRef = new CodedTokenFamily(CodedTokenFamilyType.TypeDefOrRef, 0x2, 0x2,
            MetadataTableType.TypeDef,
            MetadataTableType.TypeRef,
            MetadataTableType.TypeSpec
        );
        public static readonly CodedTokenFamily ResolutionScope = new CodedTokenFamily(CodedTokenFamilyType.ResolutionScope, 0x2, 0x2, 
            MetadataTableType.Module, 
            MetadataTableType.ModuleRef,
            MetadataTableType.AssemblyRef,
            MetadataTableType.TypeRef
        );
        static readonly CodedTokenFamily _Invalid = new CodedTokenFamily((CodedTokenFamilyType)0xFF, 0x0, 0x0);

        static readonly CodedTokenFamily[] families = new CodedTokenFamily[] {
            /* 64 */ TypeDefOrRef,
            /* 65 */ _Invalid,
            /* 66 */ _Invalid,
            /* 67 */ _Invalid,
            /* 68 */ _Invalid,
            /* 69 */ _Invalid,
            /* 70 */ _Invalid,
            /* 71 */ _Invalid,
            /* 72 */ _Invalid,
            /* 73 */ _Invalid,
            /* 74 */ _Invalid,
            /* 75 */ ResolutionScope
        };


        public static CodedTokenFamily GetByType(CodedTokenFamilyType type) {
            Throw.IfIsNotDefined(type);

            int index = (int)(type) - families[0].ID;

            Throw.ArgumentOutOfRangeExceptionIf(index < 0 || index >= families.Length);
            return families[index];
        }

    }

    

    
}
